/**
	This file is part of Fighting Field.

	Fighting Field is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	Fighting Field is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Fighting Field.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "SelectMenu.h"

#include "OgreBorderPanelOverlayElement.h"
#include "OgreTextAreaOverlayElement.h"

#include "GuiListener.h"

namespace FF
{
	SelectMenu::SelectMenu(const Ogre::String& name, const Style& style, const Ogre::DisplayString& caption, Ogre::Real width,
		Ogre::Real boxWidth, unsigned int maxItemsShown)
		: mStyle(style)
		, mHighlightIndex(0)
		, mDisplayIndex(0)
		, mDragOffset(0.0f)
	{
		mSelectionIndex = -1;
		mFitToContents = false;
		mCursorOver = false;
		mExpanded = false;
		mDragging = false;
		mMaxItemsShown = maxItemsShown;
		mItemsShown = 0;
		mElement = (Ogre::BorderPanelOverlayElement*)Ogre::OverlayManager::getSingleton().createOverlayElementFromTemplate
			(mStyle.templateName, "BorderPanel", name);
		mTextArea = (Ogre::TextAreaOverlayElement*)((Ogre::OverlayContainer*)mElement)->getChild(name + "/MenuCaption");
		mSmallBox = (Ogre::BorderPanelOverlayElement*)((Ogre::OverlayContainer*)mElement)->getChild(name + "/MenuSmallBox");
		mSmallBox->setWidth(width - 10);
		mSmallTextArea = (Ogre::TextAreaOverlayElement*)mSmallBox->getChild(name + "/MenuSmallBox/MenuSmallText");
		mElement->setWidth(width);
		mTextArea->setCharHeight(mTextArea->getCharHeight() - 3);
		mSmallTextArea->setCharHeight(mSmallTextArea->getCharHeight() - 3);

		if (boxWidth > 0)  // long style
		{
			if (width <= 0) mFitToContents = true;
			mSmallBox->setWidth(boxWidth);
			mSmallBox->setTop(2);
			mSmallBox->setLeft(width - boxWidth - 5);
			mElement->setHeight(mSmallBox->getHeight() + 4);
			mTextArea->setHorizontalAlignment(Ogre::GHA_LEFT);
			mTextArea->setAlignment(Ogre::TextAreaOverlayElement::Left);
			mTextArea->setLeft(12);
			mTextArea->setTop(10);
		}

		mExpandedBox = (Ogre::BorderPanelOverlayElement*)((Ogre::OverlayContainer*)mElement)->getChild(name + "/MenuExpandedBox");
		mExpandedBox->setWidth(mSmallBox->getWidth() + 10);
		mExpandedBox->hide();
		mScrollTrack = (Ogre::BorderPanelOverlayElement*)mExpandedBox->getChild(mExpandedBox->getName() + "/MenuScrollTrack");
		mScrollHandle = (Ogre::PanelOverlayElement*)mScrollTrack->getChild(mScrollTrack->getName() + "/MenuScrollHandle");

		SetCaption(caption);
	}

	bool SelectMenu::IsExpanded()
	{
		return mExpanded;
	}

	const Ogre::DisplayString& SelectMenu::GetCaption()
	{
		return mTextArea->getCaption();
	}

	void SelectMenu::SetCaption(const Ogre::DisplayString& caption)
	{
		mTextArea->setCaption(caption);
		if (mFitToContents)
		{
			mElement->setWidth(GetCaptionWidth(caption, mTextArea) + mSmallBox->getWidth() + 23);
			mSmallBox->setLeft(mElement->getWidth() - mSmallBox->getWidth() - 5);
		}
	}

	const Ogre::StringVector& SelectMenu::GetItems()
	{
		return mItems;
	}

	unsigned int SelectMenu::GetNumItems()
	{
		return mItems.size();
	}

	void SelectMenu::SetItems(const Ogre::StringVector& items)
	{
		mItems = items;
		mSelectionIndex = -1;

		for (unsigned int i = 0; i < mItemElements.size(); i++)   // destroy all the item elements
		{
			NukeOverlayElement(mItemElements[i]);
		}
		mItemElements.clear();

		mItemsShown = std::max<int>(2, std::min<int>(mMaxItemsShown, mItems.size()));

		for (unsigned int i = 0; i < mItemsShown; i++)   // create all the item elements
		{
			Ogre::BorderPanelOverlayElement* e =
				(Ogre::BorderPanelOverlayElement*)Ogre::OverlayManager::getSingleton().createOverlayElementFromTemplate
				(mStyle.itemTemplateName, "BorderPanel",
				mExpandedBox->getName() + "/Item" + Ogre::StringConverter::toString(i + 1));

			e->setTop(6 + i * (mSmallBox->getHeight() - 8));
			e->setWidth(mExpandedBox->getWidth() - 32);

			mExpandedBox->addChild(e);
			mItemElements.push_back(e);
		}

		if (!items.empty()) SelectItem(0, false);
		else mSmallTextArea->setCaption("");
	}

	void SelectMenu::AddItem(const Ogre::DisplayString& item)
	{
		mItems.push_back(item);
		SetItems(mItems);
	}

	void SelectMenu::RemoveItem(const Ogre::DisplayString& item)
	{
		Ogre::StringVector::iterator it;

		for (it = mItems.begin(); it != mItems.end(); it++)
		{
			if (item == *it) break;
		}

		if (it != mItems.end())
		{
			mItems.erase(it);
			if (mItems.size() < mItemsShown)
			{
				mItemsShown = mItems.size();
				NukeOverlayElement(mItemElements.back());
				mItemElements.pop_back();
			}
		}
		else 
		{
			Ogre::String desc = "Menu \"" + GetName() + "\" contains no item \"" + item + "\".";
			OGRE_EXCEPT(Ogre::Exception::ERR_ITEM_NOT_FOUND, desc, "SelectMenu::removeItem");
		}
	}

	void SelectMenu::RemoveItem(unsigned int index)
	{
		Ogre::StringVector::iterator it;
		unsigned int i = 0;

		for (it = mItems.begin(); it != mItems.end(); it++)
		{
			if (i == index) break;
			i++;
		}

		if (it != mItems.end())
		{
			mItems.erase(it);
			if (mItems.size() < mItemsShown)
			{
				mItemsShown = mItems.size();
				NukeOverlayElement(mItemElements.back());
				mItemElements.pop_back();
			}
		}
		else 
		{
			Ogre::String desc = "Menu \"" + GetName() + "\" contains no item at position " +
				Ogre::StringConverter::toString(index) + ".";
			OGRE_EXCEPT(Ogre::Exception::ERR_ITEM_NOT_FOUND, desc, "SelectMenu::removeItem");
		}
	}

	void SelectMenu::ClearItems()
	{
		mItems.clear();
		mSelectionIndex = -1;
		mSmallTextArea->setCaption("");
	}

	void SelectMenu::SelectItem(unsigned int index, bool notifyListener/* = true*/)
	{
		if (index >= mItems.size())
		{
			Ogre::String desc = "Menu \"" + GetName() + "\" contains no item at position " +
				Ogre::StringConverter::toString(index) + ".";
			OGRE_EXCEPT(Ogre::Exception::ERR_ITEM_NOT_FOUND, desc, "SelectMenu::selectItem");
		}

		mSelectionIndex = index;
		FitCaptionToArea(mItems[index], mSmallTextArea, mSmallBox->getWidth() - mSmallTextArea->getLeft() * 2);

		if (mListener && notifyListener) mListener->OnItemSelected(this);
	}

	void SelectMenu::SelectItem(const Ogre::DisplayString& item, bool notifyListener/* = true*/)
	{
		for (unsigned int i = 0; i < mItems.size(); i++)
		{
			if (item == mItems[i])
			{
				SelectItem(i, notifyListener);
				return;
			}
		}

		Ogre::String desc = "Menu \"" + GetName() + "\" contains no item \"" + item + "\".";
		OGRE_EXCEPT(Ogre::Exception::ERR_ITEM_NOT_FOUND, desc, "SelectMenu::selectItem");
	}

	Ogre::DisplayString SelectMenu::GetSelectedItem()
	{
		if (mSelectionIndex == -1)
		{
			Ogre::String desc = "Menu \"" + GetName() + "\" has no item selected.";
			OGRE_EXCEPT(Ogre::Exception::ERR_ITEM_NOT_FOUND, desc, "SelectMenu::getSelectedItem");
			return "";
		}
		else return mItems[mSelectionIndex];
	}

	int SelectMenu::GetSelectionIndex()
	{
		return mSelectionIndex;
	}

	void SelectMenu::_OnCursorPressed(const Ogre::Vector2& cursorPos)
	{
		Ogre::OverlayManager& om = Ogre::OverlayManager::getSingleton();

		if (mExpanded)
		{
			if (mScrollHandle->isVisible())   // check for scrolling
			{
				Ogre::Vector2 co = Widget::CursorOffset(mScrollHandle, cursorPos);

				if (co.squaredLength() <= 81)
				{
					mDragging = true;
					mDragOffset = co.y;
					return;
				}
				else if (Widget::IsCursorOver(mScrollTrack, cursorPos))
				{
					Ogre::Real newTop = mScrollHandle->getTop() + co.y;
					Ogre::Real lowerBoundary = mScrollTrack->getHeight() - mScrollHandle->getHeight();
					mScrollHandle->setTop((Ogre::Real)Ogre::Math::Clamp<int>((int)newTop, 0, (int)lowerBoundary));

					Ogre::Real scrollPercentage = Ogre::Math::Clamp<Ogre::Real>(newTop / lowerBoundary, 0, 1);
					SetDisplayIndex((unsigned int)(scrollPercentage * (mItems.size() - mItemElements.size()) + 0.5));
					return;
				}
			}

			if (!IsCursorOver(mExpandedBox, cursorPos, 3)) Retract();
			else
			{
				Ogre::Real l = mItemElements.front()->_getDerivedLeft() * om.getViewportWidth() + 5;
				Ogre::Real t = mItemElements.front()->_getDerivedTop() * om.getViewportHeight() + 5;
				Ogre::Real r = l + mItemElements.back()->getWidth() - 10;
				Ogre::Real b = mItemElements.back()->_getDerivedTop() * om.getViewportHeight() +
					mItemElements.back()->getHeight() - 5;

				if (cursorPos.x >= l && cursorPos.x <= r && cursorPos.y >= t && cursorPos.y <= b)
				{
					if (mHighlightIndex != mSelectionIndex) SelectItem(mHighlightIndex);
					Retract();
				}
			}
		}
		else
		{
			if (mItems.size() < 2) return;   // don't waste time showing a menu if there's no choice

			if (IsCursorOver(mSmallBox, cursorPos, 4))
			{
				mExpandedBox->show();
				mSmallBox->hide();

				// calculate how much vertical space we need
				Ogre::Real idealHeight = mItemsShown * (mSmallBox->getHeight() - 8) + 20;
				mExpandedBox->setHeight(idealHeight);
				mScrollTrack->setHeight(mExpandedBox->getHeight() - 20);

				mExpandedBox->setLeft(mSmallBox->getLeft() - 4);

				// if the expanded menu goes down off the screen, make it go up instead
				if (mSmallBox->_getDerivedTop() * om.getViewportHeight() + idealHeight > om.getViewportHeight())
				{
					mExpandedBox->setTop(mSmallBox->getTop() + mSmallBox->getHeight() - idealHeight + 3);
					// if we're in thick style, hide the caption because it will interfere with the expanded menu
					if (mTextArea->getHorizontalAlignment() == Ogre::GHA_CENTER) mTextArea->hide();
				}
				else mExpandedBox->setTop(mSmallBox->getTop() + 3);

				mExpanded = true;
				mHighlightIndex = mSelectionIndex;
				SetDisplayIndex(mHighlightIndex);

				if (mItemsShown < mItems.size())  // update scrollbar position
				{
					mScrollHandle->show();
					Ogre::Real lowerBoundary = mScrollTrack->getHeight() - mScrollHandle->getHeight();
					mScrollHandle->setTop((Ogre::Real)(int)(mDisplayIndex * lowerBoundary / (mItems.size() - mItemElements.size())));
				}
				else mScrollHandle->hide();
			}
		}
	}

	void SelectMenu::_OnCursorReleased(const Ogre::Vector2& cursorPos)
	{
		mDragging = false;
	}

	void SelectMenu::_OnCursorMoved(const Ogre::Vector2& cursorPos)
	{
		Ogre::OverlayManager& om = Ogre::OverlayManager::getSingleton();

		if (mExpanded)
		{
			if (mDragging)
			{
				Ogre::Vector2 co = Widget::CursorOffset(mScrollHandle, cursorPos);
				Ogre::Real newTop = mScrollHandle->getTop() + co.y - mDragOffset;
				Ogre::Real lowerBoundary = mScrollTrack->getHeight() - mScrollHandle->getHeight();
				mScrollHandle->setTop((Ogre::Real)Ogre::Math::Clamp<int>((int)newTop, 0, (int)lowerBoundary));

				Ogre::Real scrollPercentage = Ogre::Math::Clamp<Ogre::Real>(newTop / lowerBoundary, 0, 1);
				int newIndex = (int) (scrollPercentage * (mItems.size() - mItemElements.size()) + 0.5);
				if (newIndex != mDisplayIndex) SetDisplayIndex(newIndex);
				return;
			}

			Ogre::Real l = mItemElements.front()->_getDerivedLeft() * om.getViewportWidth() + 5;
			Ogre::Real t = mItemElements.front()->_getDerivedTop() * om.getViewportHeight() + 5;
			Ogre::Real r = l + mItemElements.back()->getWidth() - 10;
			Ogre::Real b = mItemElements.back()->_getDerivedTop() * om.getViewportHeight() +
				mItemElements.back()->getHeight() - 5;

			if (cursorPos.x >= l && cursorPos.x <= r && cursorPos.y >= t && cursorPos.y <= b)
			{
				int newIndex = (int)(mDisplayIndex + (cursorPos.y - t) / (b - t) * mItemElements.size());
				if (mHighlightIndex != newIndex)
				{
					mHighlightIndex = newIndex;
					SetDisplayIndex(mDisplayIndex);
				}
			}
		}
		else
		{
			if (IsCursorOver(mSmallBox, cursorPos, 4))
			{
				mSmallBox->setMaterialName(mStyle.smallBoxOverMaterialName);
				mSmallBox->setBorderMaterialName(mStyle.smallBoxOverBorderMaterialName);
				mCursorOver = true;
			}
			else
			{
				if (mCursorOver)
				{
					mSmallBox->setMaterialName(mStyle.smallBoxUpMaterialName);
					mSmallBox->setBorderMaterialName(mStyle.smallBoxUpBorderMaterialName);
					mCursorOver = false;
				}
			}
		}
	}

	void SelectMenu::_OnFocusLost()
	{
		if (mExpandedBox->isVisible()) Retract();
	}

	void SelectMenu::SetDisplayIndex(unsigned int index)
	{
		index = std::min<int>(index, mItems.size() - mItemElements.size());
		mDisplayIndex = index;
		Ogre::BorderPanelOverlayElement* ie;
		Ogre::TextAreaOverlayElement* ta;

		for (int i = 0; i < (int)mItemElements.size(); i++)
		{
			ie = mItemElements[i];
			ta = (Ogre::TextAreaOverlayElement*)ie->getChild(ie->getName() + "/MenuItemText");

			FitCaptionToArea(mItems[mDisplayIndex + i], ta, ie->getWidth() - 2 * ta->getLeft());

			if ((mDisplayIndex + i) == mHighlightIndex)
			{
				ie->setMaterialName(mStyle.itemOverMaterialName);
				ie->setBorderMaterialName(mStyle.itemOverBorderMaterialName);
			}
			else
			{
				ie->setMaterialName(mStyle.itemUpMaterialName);
				ie->setBorderMaterialName(mStyle.itemOverBorderMaterialName);
			}
		}
	}

	void SelectMenu::Retract()
	{
		mDragging = false;
		mExpanded = false;
		mExpandedBox->hide();
		mTextArea->show();
		mSmallBox->show();
		mSmallBox->setMaterialName("SdkTrays/MiniTextBox");
		mSmallBox->setBorderMaterialName("SdkTrays/MiniTextBox");
	}
}
